package org.boticelli.plugin.chat;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.boticelli.Bot;
import org.boticelli.util.RandomUtil;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;

public class ChatMessageHubImpl
    implements ChatMessageHub, InitializingBean, ApplicationListener
{
    protected static Logger log = Logger.getLogger(ChatMessageHubImpl.class);

    private long timeout = 30 * 1000;

    private int maxBots = 10;

    private Map<String, ChatBotEntry> chatBotEntries = Collections
        .synchronizedMap(new HashMap<String, ChatBotEntry>());

    private HubWatchDog watchdog;

    /**
     * Sets the maximum time a request will wait before giving up and returning
     * an empty message array
     *
     * @param timeout
     */
    public void setTimeout(long timeout)
    {
        this.timeout = timeout;
    }

    /**
     * Sets the maximum number of bots allowed.
     *
     * @param maxBots
     */
    public void setMaxBots(int maxBots)
    {
        this.maxBots = maxBots;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.boticelli.plugin.chat.ChatMessageHub#send(java.lang.String,
     *      org.boticelli.plugin.chat.ChatMessage)
     */
    public void queueMessage(String secret, ChatMessage msg) throws InvalidSecretException
    {
        if (log.isDebugEnabled())
        {
            log.debug("enqueuing " + msg);
        }

        ChatBotEntry entry = entry(secret);
        entry.queueMessage(msg);

        if (log.isDebugEnabled())
        {
            log.debug("exit enqueuing " + msg);
        }
    }

    private ChatBotEntry entry(String secret) throws InvalidSecretException
    {
        ChatBotEntry entry = chatBotEntries.get(secret);
        if (entry == null)
        {
            throw new InvalidSecretException();
        }
        entry.access();
        return entry;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.boticelli.plugin.chat.ChatMessageHub#getMessages(java.lang.String)
     */
    public List<ChatMessage> getMessages(String secret) throws InvalidSecretException
    {
        if (log.isDebugEnabled())
        {
            log.debug("getting messages for secret " + secret);
        }

        ChatBotEntry entry = entry(secret);
        List<ChatMessage> messages = entry.messages(timeout);

        if (log.isDebugEnabled())
        {
            log.debug("got messages "+messages);
        }

        return messages;
    }

    public String enter(String name, Bot bot) throws Exception
    {
        if (chatBotEntries.size() > maxBots)
        {
            throw new IllegalStateException("Max bots = " + maxBots +
                " reached. No more chat bots can be created.");
        }

        String secret = RandomUtil.createWord(32);
        Bot chatBot = bot.spawn( ChatBot.class, name, new BoticelliChatPlugin(this, secret));
        chatBotEntries.put(secret, new ChatBotEntry(chatBot));
        return secret;
    }

    public void send(String secret, String message) throws InvalidSecretException
    {
        if (log.isDebugEnabled())
        {
            log.debug("enter send "+message);
        }
        ChatBotEntry entry = entry(secret);
        entry.send(message);
        if (log.isDebugEnabled())
        {
            log.debug("exit send "+message);
        }
    }

    public void afterPropertiesSet() throws Exception
    {
        this.watchdog = new HubWatchDog();
        this.watchdog.start();
    }

    private void disconnectBots(Long maxDelta)
    {
        if (log.isDebugEnabled())
        {
            log.debug("enter disconnectBots("+maxDelta+")");
        }

        synchronized (chatBotEntries)
        {
            long now = System.currentTimeMillis();
            List<String> botsToBeRemoved = new ArrayList<String>();
            for (Map.Entry<String,ChatBotEntry> e : chatBotEntries.entrySet())
            {
                String secret = e.getKey();
                ChatBotEntry entry = e.getValue();

                long accessDelta = now - entry.getLastAccess();
                if (accessDelta > maxDelta)
                {
                    botsToBeRemoved.add(secret);
                }
            }

            for (String secret : botsToBeRemoved)
            {
                ChatBotEntry entry = chatBotEntries.remove(secret);
                entry.disconnect();
            }
        }
        if (log.isDebugEnabled())
        {
            log.debug("exit disconnectBots("+maxDelta+")");
        }
    }

    private class HubWatchDog
        extends Thread
    {

        public HubWatchDog()
        {
            super("HubWatchDog");
        }

        @Override
        public void run()
        {
            try
            {
                while (!interrupted())
                {
                    Thread.sleep(timeout);

                    if (log.isDebugEnabled())
                    {
                        log.debug("checking bot access");
                    }

                    disconnectBots(timeout * 2);
                }
            }
            catch (InterruptedException e)
            {
                log.info("watchdog interrupted ", e);
            }
            log.info("exiting watchdog");
        }
    }

    public void onApplicationEvent(ApplicationEvent applicationEvent)
    {
        if (applicationEvent instanceof ContextClosedEvent)
        {
            this.watchdog.interrupt();

            // disconnect all bots
            disconnectBots(Long.MIN_VALUE);
        }
    }
}
